﻿#if UNITY_EDITOR
using UnityEditor;
#endif
using System.Collections.Generic;
using UnityEngine;
using Devil;

namespace GameToolkit
{
    public static class ComponentUtil
    {

        public const string TAG_MAIN_CAMERA = "MainCamera";

        static Camera[] sAllCameras;

        public static Camera MainCamera
        {
            get
            {
                Camera cam = Camera.main;
                if (cam && cam.isActiveAndEnabled)
                    return cam;
                var count = Camera.allCamerasCount;
                if (sAllCameras == null || sAllCameras.Length < count)
                    sAllCameras = new Camera[count];
                var len = Camera.GetAllCameras(sAllCameras);
                int want = 0;
                for (int i = 0; i < len; i++)
                {
                    var tmp = sAllCameras[i];
                    sAllCameras[i] = null;
                    int w = tmp.MostWantedValueForMain();
                    if (w > want)
                    {
                        want = w;
                        cam = tmp;
                    }
                }
                return cam;
            }
        }

        public static void ExecuteOnAllCameras(System.Action<Camera> action)
        {
            if (action == null)
                return;
            var count = Camera.allCamerasCount;
            if (sAllCameras == null || sAllCameras.Length < count)
                sAllCameras = new Camera[count];
            var len = Camera.GetAllCameras(sAllCameras);
            for (int i = 0; i < len; i++)
            {
                var tmp = sAllCameras[i];
                sAllCameras[i] = null;
                action(tmp);
            }
        }

        public static Camera ActiveCameraForLayer(int layer)
        {
            var count = Camera.allCamerasCount;
            if (sAllCameras == null || sAllCameras.Length < count)
                sAllCameras = new Camera[count];
            var len = Camera.GetAllCameras(sAllCameras);
            for (int i = 0; i < len; i++)
            {
                Camera c = sAllCameras[i];
                sAllCameras[i] = null;
                if (c.isActiveAndEnabled && (c.cullingMask & (1 << layer)) != 0)
                {
                    if (i < len - 1)
                        System.Array.Clear(sAllCameras, i + 1, len - i - 1);
                    return c;
                }
            }
            return null;
        }

        static int MostWantedValueForMain(this Camera cam)
        {
            if (!cam)
                return 0;
            int n = 1;
            if (cam.isActiveAndEnabled)
                n |= 0x40000;
            if (cam.targetTexture == null)
                n |= 0x20000;
            if(cam.CompareTag(TAG_MAIN_CAMERA))
                n |= 0x10000;
            if (cam.clearFlags == CameraClearFlags.Skybox)
                n |= 0x8000;
            if (cam.clearFlags == CameraClearFlags.SolidColor)
                n |= 0x4000;
            return n;
        }

        public static void AlignPivot(Transform trans, Vector3 between)
        {
            var len = trans.childCount;
            if (len == 0)
                return;
            var bounds = new Bounds(trans.GetChild(0).position, Vector3.zero);
            for (int i = 1; i < len; i++)
            {
                var child = trans.GetChild(i);
                if (child.gameObject.activeSelf)
                    bounds.Encapsulate(child.position);
            }
            Vector3 pos = MathExt.Mul(bounds.size, between) + bounds.min;
            var delta = trans.position - pos;
            delta = trans.worldToLocalMatrix.MultiplyVector(delta);
#if UNITY_EDITOR
            var undo = "AlignPivot";
            Undo.RecordObject(trans, undo);
#endif
            trans.position = pos;
#if UNITY_EDITOR
            EditorUtility.SetDirty(trans);
#endif
            for (int i = 0; i < len; i++)
            {
                var child = trans.GetChild(i);
#if UNITY_EDITOR
                Undo.RecordObject(child, undo);
#endif
                child.localPosition += delta;
#if UNITY_EDITOR
                EditorUtility.SetDirty(child);
#endif
            }
        }

        /// <summary>
        /// 单位化本地缩放，同时缩放子节点，保证子节点尽可能在世界空间中保持不变
        /// </summary>
        /// <param name="trans"></param>
        public static void UnifyTransformScale(Transform trans)
        {
            var scale = trans.localScale;
            Matrix4x4 selfMat = trans.localToWorldMatrix;
            List<Matrix4x4> matrix = new List<Matrix4x4>(trans.childCount);
            List<Transform> children = new List<Transform>(trans.childCount);
            for (int i = 0; i < trans.childCount; i++)
            {
                var t = trans.GetChild(i);
                matrix.Add(t.localToWorldMatrix);
                children.Add(t);
            }
#if UNITY_EDITOR
            Undo.RecordObject(trans, "UnifyScale");
#endif
            trans.localScale = Vector3.one;

#if UNITY_EDITOR
            EditorUtility.SetDirty(trans);
#endif
            List<BoxCollider> boxes = new List<BoxCollider>();
            trans.GetComponents<BoxCollider>(boxes);
            selfMat = trans.worldToLocalMatrix * selfMat;
            foreach (var box in boxes)
            {
#if UNITY_EDITOR
                Undo.RecordObject(box, "UnifyScale");
#endif
                box.center = selfMat.MultiplyPoint(box.center);
                box.size = MathExt.Mul(box.size, scale);

#if UNITY_EDITOR
                EditorUtility.SetDirty(box);
#endif
            }

            for (int i = 0; i < children.Count; i++)
            {
#if UNITY_EDITOR
                Undo.RecordObject(children[i], "UnifyScale");
#endif
                children[i].localScale = MathExt.Mul(scale, children[i].localScale);
                children[i].position = matrix[i].MultiplyPoint(Vector3.zero);
                children[i].rotation = matrix[i].rotation;
#if UNITY_EDITOR
                EditorUtility.SetDirty(children[i]);
#endif
            }

        }


        public static string GetRelativePath(Transform parent, Transform child, string prefix = null)
        {
            if (child == null || parent == null || !child.IsChildOf(parent))
                return prefix;
            var buf = ParallelUtils.GetBuilder();
            var tmp = child;
            while (tmp != null && tmp != parent)
            {
                if (buf.Length > 0)
                    buf.Insert(0, '/');
                buf.Insert(0, tmp.name);
                tmp = tmp.parent;
            }
            if (!string.IsNullOrEmpty(prefix))
                buf.Insert(0, prefix);
            return ParallelUtils.ReturnRelease(buf);
        }

        public static int GetRelativeDepth(Transform parent, Transform child)
        {
            if (parent == null || child == null)
                return 0;
            var depth = 0;
            var node = child.parent;
            while(node != null)
            {
                depth++;
                if (node == parent)
                    return depth;
                else
                    node = node.parent;
            }
            return 0;
        }

        public static T GetOrAddComponent<T>(this GameObject go) where T : Component
        {
            T com;
            if(!go.TryGetComponent<T>(out com))
                com = go.AddComponent<T>();
            return com;
        }

        public static T GetComponentInChildren<T>(this Transform root, string path) where T : Component
        {
            if (root == null)
                return null;
            var trans = root.Find(path);
            T com;
            if (trans != null && trans.TryGetComponent<T>(out com))
                return com;
            else
                return null;
        }

        public static T GetComponentInParent<T>(this Transform trans, bool considerSelf = false) where T : Component
        {
            T cmp = null;
            if (trans)
            {
                Transform root;
                if (considerSelf)
                {
                    root = trans;
                }
                else
                {
                    root = trans.parent;
                }
                while (root)
                {
                    if (root.TryGetComponent<T>(out cmp))
                        return cmp;
                    root = root.parent;
                }
            }
            return cmp;
        }

        public static Transform MatchRecursive(Transform root, FilterDelegate<Transform> filter)
        {
            if (filter(root))
            {
                return root;
            }
            else
            {
                int len = root.childCount;
                for (int i = 0; i < len; i++)
                {
                    Transform trans = root.GetChild(i);
                    trans = MatchRecursive(trans, filter);
                    if (trans != null)
                        return trans;
                }
                return null;
            }
        }
        public static Bounds CalcuateBounds(Matrix4x4 matrix, Vector3 localCenter, Vector3 localSize)
        {
            var bd = new Bounds(matrix.MultiplyPoint(localCenter), Vector3.zero);
            var halfsize = localSize * 0.5f;
            var vec = halfsize;
            bd.Encapsulate(matrix.MultiplyPoint(localCenter + vec));
            bd.Encapsulate(matrix.MultiplyPoint(localCenter - vec));
            vec = new Vector3(-halfsize.x, halfsize.y, halfsize.z);
            bd.Encapsulate(matrix.MultiplyPoint(localCenter + vec));
            bd.Encapsulate(matrix.MultiplyPoint(localCenter - vec));
            vec = new Vector3(-halfsize.x, halfsize.y, -halfsize.z);
            bd.Encapsulate(matrix.MultiplyPoint(localCenter + vec));
            bd.Encapsulate(matrix.MultiplyPoint(localCenter - vec));
            vec = new Vector3(halfsize.x, halfsize.y, -halfsize.z);
            bd.Encapsulate(matrix.MultiplyPoint(localCenter + vec));
            bd.Encapsulate(matrix.MultiplyPoint(localCenter - vec));
            return bd;
        }
        public static T FindComponentInScene<T>() where T : Component
        {
            return Object.FindObjectOfType<T>();
        }

        public static void ResetTransform(Transform trans)
        {
            trans.localPosition = Vector3.zero;
            trans.localRotation = Quaternion.identity;
            trans.localScale = Vector3.one;
        }

        public static bool IsActiveSelection(this GameObject go)
        {
#if UNITY_EDITOR
            var sel = UnityEditor.Selection.activeGameObject;
            if (sel == go)
                return true;
            if (go != null && sel != null && sel.transform.IsChildOf(go.transform))
                return true;
            return false;
#else
            return false;
#endif
        }

#if UNITY_EDITOR

        [MenuItem("Assets/Utils/Delete Selected Sub Assets")]
        static void DeleteSubAssets()
        {
            var sel = Selection.objects;
            var pathes = new HashSet<string>();
            foreach (var t in sel)
            {
                //if (!(t is ScriptableObject))
                //    continue;
                var path = AssetDatabase.GetAssetPath(t);
                if (!string.IsNullOrEmpty(path))
                {
                    pathes.Add(path);
                    AssetDatabase.RemoveObjectFromAsset(t);
                    if (t != null)
                        Object.DestroyImmediate(t, true);
                }
            }
            foreach (var path in pathes)
            {
                AssetDatabase.ImportAsset(path);
            }
        }


        [MenuItem("CONTEXT/Transform/Unify Local Scale")]
        static void UnifyScale(MenuCommand command)
        {
            var trans = command.context as Transform;
            UnifyTransformScale(trans);
        }


        [MenuItem("CONTEXT/Transform/Align Pivot to Center")]
        static void AlginPivotToCenter(MenuCommand command)
        {
            AlignPivot(command.context as Transform, new Vector3(0.5f, 0.5f, 0.5f));
        }

        [MenuItem("CONTEXT/Transform/Align Pivot to Bottom")]
        static void AlignPivotToBottom(MenuCommand command)
        {
            AlignPivot(command.context as Transform, new Vector3(0.5f, 0f, 0.5f));
        }

        [MenuItem("CONTEXT/Transform/Set as Prefabs' Parent")]
        static void GetPrefabTransforms(MenuCommand command)
        {
            var baseTrans = command.context as Transform;
            var len = baseTrans.childCount;
            var children = new List<Transform>(len);

            for (int i = 0; i < len; i++)
            {
                children.Add(baseTrans.GetChild(i));
            }
            foreach (var t in children)
            {
                GetPrefabTranforms(baseTrans, t);
            }
        }

        public static void GetPrefabTranforms(Transform baseTrans, Transform child)
        {
            var pref = PrefabUtility.GetOutermostPrefabInstanceRoot(child.gameObject);
            if (pref == null)
            {
                var len = child.childCount;
                var children = new List<Transform>(len);
                for (int i = 0; i < len; i++)
                {
                    children.Add(child.GetChild(i));
                }
                foreach (var t in children)
                {
                    GetPrefabTranforms(baseTrans, t);
                }
            }
            else if (pref.transform != baseTrans && pref.transform.IsChildOf(baseTrans))
            {
                Undo.RecordObject(child, "ReParent");
                child.SetParent(baseTrans, true);
            }
        }


        public static GameObject GetRootAsPrefab(GameObject go, GameObject baseGameObject = null)
        {
            var root = PrefabUtility.GetOutermostPrefabInstanceRoot(go);
            while (root != null)
            {
                var p = root.transform.parent;
                var parent = p == null ? null : PrefabUtility.GetOutermostPrefabInstanceRoot(p.gameObject);
                if (parent != null)
                {
                    if (baseGameObject != null && parent != baseGameObject && !parent.transform.IsChildOf(baseGameObject.transform))
                        break;
                    var relativePath = ComponentUtil.GetRelativePath(parent.transform, root.transform);
                    var prefabpath = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(parent);
                    var pref = AssetDatabase.LoadAssetAtPath<GameObject>(prefabpath);
                    if (pref != null && pref.transform.Find(relativePath) != null)
                        root = parent;
                }
            }
            return root;
        }

        public static void CopySerializedPropertes(UnityEngine.Object source, UnityEngine.Object destination)
        {
            if (source == null || destination == null)
                return;
            var src = new SerializedObject(source);
            var dest = new SerializedObject(destination);
            var prop = src.FindProperty("m_EditorClassIdentifier");
            prop.Next(true);
            bool dirty = false;
            while (prop.Next(true))
            {
                var destp = dest.FindProperty(prop.propertyPath);
                if (destp == null || destp.type != prop.type || destp.propertyType != prop.propertyType)
                    continue;
                switch (destp.propertyType)
                {
                    case SerializedPropertyType.Integer:
                        destp.intValue = prop.intValue;
                        //destp.longValue = prop.longValue;
                        dirty = true;
                        break;
                    case SerializedPropertyType.Boolean:
                        destp.boolValue = prop.boolValue;
                        dirty = true;
                        break;
                    case SerializedPropertyType.Float:
                        destp.floatValue = prop.floatValue;
                        dirty = true;
                        break;
                    case SerializedPropertyType.String:
                        destp.stringValue = prop.stringValue;
                        dirty = true;
                        break;
                    case SerializedPropertyType.Color:
                        destp.colorValue = prop.colorValue;
                        dirty = true;
                        break;
                    case SerializedPropertyType.ObjectReference:
                        destp.objectReferenceValue = prop.objectReferenceValue;
                        dirty = true;
                        break;
                    case SerializedPropertyType.LayerMask:
                    case SerializedPropertyType.Enum:
                        destp.intValue = prop.intValue;
                        dirty = true;
                        break;
                    case SerializedPropertyType.Vector2:
                        destp.vector2Value = prop.vector2Value;
                        dirty = true;
                        break;
                    case SerializedPropertyType.Vector3:
                        destp.vector3Value = prop.vector3Value;
                        dirty = true;
                        break;
                    case SerializedPropertyType.Vector4:
                        destp.vector4Value = prop.vector4Value;
                        dirty = true;
                        break;
                    case SerializedPropertyType.Rect:
                        destp.rectValue = prop.rectValue;
                        dirty = true;
                        break;
                    case SerializedPropertyType.ArraySize:
                        destp.intValue = prop.intValue;
                        dirty = true;
                        break;
                    case SerializedPropertyType.AnimationCurve:
                        destp.animationCurveValue = prop.animationCurveValue;
                        dirty = true;
                        break;
                    case SerializedPropertyType.Bounds:
                        destp.boundsValue = prop.boundsValue;
                        dirty = true;
                        break;
                    default:
                        // 待续 ...
                        break;
                }
            }
            if (dirty)
                dest.ApplyModifiedProperties();
        }

        public static void CreateAssetAt<T>(string path, string name) where T : ScriptableObject
        {
            var t = ScriptableObject.CreateInstance<T>();
            t.name = name;
            if (System.IO.File.Exists(path))
            {
                UnityEditor.AssetDatabase.AddObjectToAsset(t, path);
            }
            else
            {
                UnityEditor.AssetDatabase.CreateAsset(t, path);
            }
            UnityEditor.AssetDatabase.ImportAsset(path);
        }
#endif
    }
}
